#include "as2_t1.h"
#include <stddef.h>
#include <errno.h>
#include <assert.h>

/* deze macro zorgt ervoor dat het geheugenblok als een circulaire buffer doorlopen wordt */
#define increment(x) x = (x+1)%mem_size

/* het geheugen wordt opgedeeld in stukken die een task_t bevatten en een indicatie of het stuk in gebruik is */
typedef struct mem_struct {
	char in_use;
	task_t task;

} mem_t;

typedef unsigned long index_t;
typedef mem_t *mem_ptr;
typedef char *char_ptr;

/* memory is een pointer naar het geheugenblok */
mem_ptr memory = NULL;
/* mem_size bevat de grootte van het geheugenblok, uitgedrukt in mem_t's */
size_t mem_size;

/* current_index houdt bij waar de functie moet beginnen te zoeken naar vrij geheugen */
index_t current_index;

/*
 *
 */
void *task_alloc() {

	/* vraag het geheugenblok op als deze functie voor het eerst wordt aangeroepen */
	if (memory == NULL) {
		memory = (mem_ptr)get_MEM_BLOCK_START();
		mem_size = MEM_BLOCK_SIZE/sizeof(mem_t);
		current_index = 0;

		assert(memory != NULL);
		assert(mem_size > 0);
	}

	/* kijk of het huidige stuk geheugen in gebruik is */
	if (memory[current_index].in_use == 'y') {
		/* houd dit bezette stuk geheugen in de gaten en verplaats current_index */
		index_t mem_first_checked = current_index;
		increment(current_index);

		/* blijf door het geheugen lopen tot er een stukje niet in gebruik is of de index terug is bij het eerste stuk geheugen */
		while (memory[current_index].in_use == 'y' && current_index != mem_first_checked)
			increment(current_index);

		/* geef een error terug als er geen vrij stuk geheugen is gevonden */
		if (current_index == mem_first_checked) {
			errno = ENOMEM;
			return NULL;
		}
	}

	/* vrij stuk geheugen gevonden, dus markeer het nu als in gebruik */
	memory[current_index].in_use = 'y';

	/* maak een pointer naar de task in het vrije geheugen */
	void *ptr = (void*)&(memory[current_index].task);

	/* schuif current_index alvast op voor de volgende keer */
	increment(current_index);

	return ptr;
}

void task_free(void *ptr) {
	/* doe niks als er geen geheugen is gealloceerd of de pointer naar NULL verwijst */
	if (memory == NULL || ptr == NULL)
		return;

	/* vorm de pointer om naar een index in het geheugen */
	size_t offset = (size_t)ptr - offsetof(mem_t, task) - (size_t)memory;
	/* (controleer of de pointer wel goed uitgelijnd staat) */
	/* assert((offset % sizeof(mem_t)) == 0); */
	if ((offset % sizeof(mem_t)) != 0)
		return;

	index_t index = (index_t)(offset/sizeof(mem_t));

	/* geef het stukje geheugen vrij als het binnen de grenzen van het geheugen ligt */
	if (index >= 0 && index < mem_size) {
		memory[index].in_use = 'n';

		/* dit stukje geheugen is per definitie vrij, dus plaats de current_index hier */
		current_index = index;
	}
}
